package com.guessdroid.sensing;

import java.util.ArrayList;

public class DataPoints implements java.io.Serializable
{
        private static final long serialVersionUID = 7364069278872060034L;
        private ArrayList<DataPoint> p; //ideally no name, the class name should be enough
        public ArrayList<AbstractSensor> abstractSensors=null;
        
    	// Creates an initial empty set of datapoints
    	// Intended to be called when initializing, or when doing a reset after forgetting all previous data
    	public DataPoints()
    	{
    		p=new ArrayList<DataPoint>();
    		this.abstractSensors=getSensors();
    	}
    	
    	private ArrayList<AbstractSensor> getSensors(){ 
        	ArrayList<AbstractSensor> sensors = new ArrayList<AbstractSensor>();
        	sensors.add(new AS_avgAudioVolume());
        	sensors.add(new AS_avgAccChange());
        	sensors.add(new AS_wifiStationAmount());
        	return sensors;
        }
       
        // Processes raw data with abstract sensors into a set of data points
        // each representing ~interval*0,52ms worth of source data (use 1907 for roughly one second)
        // Order and content of abstractSensors defines the internal structure of a DataPoint
        public DataPoints(RawData source, int interval)
        {
        		this.abstractSensors= getSensors();
                ArrayList<ArrayList<Float>> dimensionResults = new ArrayList<ArrayList<Float>>();
                int i=0;
                while (i<abstractSensors.size())
                {
                        dimensionResults.add(abstractSensors.get(i).process(source, interval));
                        i++;
                }
                int points=0;i=0;
                if (dimensionResults.size()>0) {points=dimensionResults.get(0).size();}
                while (i<dimensionResults.size())
                {
                        if (points>dimensionResults.get(i).size()) {points=dimensionResults.get(i).size();}
                        i++;
                }
                p=new ArrayList<DataPoint>(points);
                while (points>0)
                {
                        DataPoint dp=new DataPoint();
                        i=0;
                        while (i<dimensionResults.size())
                        {
                                dp.d.add(dimensionResults.get(i).remove(0));
                                i++;
                        }
                        p.add(dp);
                        points--;
                }
        }
       
        public DataPoints(DataPoints copy)
        {
        		abstractSensors=copy.abstractSensors;
                p=new ArrayList<DataPoint>(copy.p.size());
                int i=0;
                while (i<copy.p.size())
                {
                        DataPoint dp=new DataPoint();
                        dp.labelId=copy.p.get(i).labelId;
                        int ii=0;
                        while (ii<copy.p.get(0).d.size())
                        {
                                dp.d.add(copy.p.get(i).d.get(ii));
                                ii++;
                        }
                        p.add(dp);
                        i++;
                }
        }
       
        // Counts the amount of data points with the given label
        // If there are no points, return -1 always
        // If queried with 0, return the amount of unknown points
        // If queried with -1, return the amount of points-to-be-guessed
        // If queried with -2, return the amount of all points
        public int countLabels(int query)
        {
                if (p.size()==0) {return -1;}
                if (query==-2) {return p.size();}
                int count=0;
                int i=0;
                while (i<p.size())
                {
                        if (p.get(i).labelId==query) {count++;}
                        i++;
                }
                return count;
        }
       
        // Inserts all data points from the given set into this set. The other set does not change
        // No check is done to data point dimension validity / abstract sensors used
        public void merge(DataPoints others)
        {
                int i=0;
                while (i<others.p.size())
                {
                        p.add(others.p.get(i));
                        i++;
                }
        }
       
        // Sets the given labelId for all the points with labelId -1
        public void setGuessResult(int newId)
        {
                int i=0;
                while (i<p.size())
                {
                        if (p.get(i).labelId==-1) {p.get(i).labelId=newId;}
                        i++;
                }
        }
       
        // Prepares the data points for a guess by weighing the dimensions "equally"
        // Called automatically during guess()
        // Points of the resulting set should not be compared with any other points
        private void standardize()
        {
                float[] mean=new float[p.get(0).d.size()];
                float[] dev =new float[p.get(0).d.size()];
                int i=0;
                while (i<mean.length) {mean[i]=0;dev[i]=0;i++;}
                i=0;
                while (i<p.size()) // calculate mean
                {
                        int ii=0;
                        while (ii<mean.length)
                        {
                                mean[ii]+=p.get(i).d.get(ii);
                                ii++;
                        }
                        i++;
                }
                i=0;
                while (i<mean.length) {mean[i]/=p.size();i++;} // done
                i=0;
                while (i<p.size()) // calculate sample standard deviation
                {
                        int ii=0;
                        while (ii<dev.length)
                        {
                                dev[ii]+=(p.get(i).d.get(ii)-mean[ii])*(p.get(i).d.get(ii)-mean[ii]);
                                ii++;
                        }
                        i++;
                }
                i=0;
                while (i<dev.length)
                {
                        dev[i]/=p.size()-1; // crashes with one -1 point, ask a statistician why
                        dev[i]=(float)Math.sqrt(dev[i]);
                        i++;
                } // done
                i=0;
                while (i<p.size()) // actual standardization
                {
                        int ii=0;
                        while (ii<mean.length)
                        {
                                p.get(i).d.set(ii,(p.get(i).d.get(ii)-mean[ii])/dev[ii]);
                                ii++;
                        }
                        i++;
                }
        }
       
        // manhattan distance. got a problem with that?
        private float distance(DataPoint p1, DataPoint p2)
        {
                float ret=0;
                int i=0;
                while (i<p1.d.size())
                {
                        float temp=p1.d.get(i)-p2.d.get(i);
                        ret+=temp>0?temp:-temp;
                        i++;
                }
                return ret;
        }
       
        // Gives a guess of the real labelId of those points that have labelId -1
        // Nearest neighbour -type algorithm. The set should have points with labelId > 0
        // The -1 points decide the result with a majority vote, in a conference room
        // Unknown points ignored for now
        // Throws exception if no points with labelId -1
        public int guess() throws Exception
        {
                if (countLabels(-1)<1) {throw new Exception();}
                ArrayList<Integer> votes=new ArrayList<Integer>();
                DataPoints copy=new DataPoints(this);
                copy.standardize();
                int turn=0; // whose turn it is to guess
                while (turn<p.size()) // finds the nearest neighbour
                {
                        if (p.get(turn).labelId!=-1) {turn++;continue;}
                        int other=0;Float closest_d=null;Integer closest=null;
                        while (other<p.size()) // compare to all other points with a label..
                        {
                                if (p.get(other).labelId==-1||p.get(other).labelId==0) {other++;continue;}
                                // distance is calculated between standardized points
                                float dist=distance(copy.p.get(turn),copy.p.get(other));
                                if (closest_d==null)
                                {
                                        closest_d=dist;
                                        closest=p.get(other).labelId;
                                }
                                else if (closest_d>dist)
                                {
                                        closest_d=dist;
                                        closest=p.get(other).labelId;
                                }
                                other++;
                        }
                        if (closest!=null) {votes.add(closest);}
                        turn++;
                }
                if (votes.size()==0) {return 0;} // no labeled points known
                int guess=0;
                int guess2;
                int count1=0;
                int count2=0;
                while (votes.size()!=0) // majority vote
                {
                        guess2=votes.remove(0);count2++;
                        int i=0;
                        while (i<votes.size())
                        {
                                if (votes.get(i)==guess2) {votes.remove(0);count2++;} else {i++;}
                        }
                        if (count2>count1) {count1=count2;guess=guess2;count2=0;}
                }
                return guess;
        }
}